﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using HighSign.Common;
using HighSign.Common.Gestures;
using HighSign.Common.Plugins;
using System.Drawing;
using HighSign.PointPatterns;
using HighSign.Common.Input;
using HighSign.Plugins;

namespace HighSign.Gestures
{
    /// <summary>
    /// workflow:
    /// <para>
    /// [showhint]
    /// </para>
    /// <para>
    /// while(mouse button is not released)
    /// </para>
    /// <para>{</para>
    /// <para>    > mouse move </para>
    /// <para>    > MouseCapture.Instance.PointCaptured </para>
    /// <para>    > get the name of gesture</para>
    /// <para>    > if gesture undefined, update hint </para>
    /// <para>    > if gesture defined, PluginManager process the gesture </para>
    /// <para>    > PluginManager_GestureRecognized</para>
    /// <para>    > update hint</para>
    /// }
    /// <para>> perform the plugin's execution related to the gesture</para>
    /// </summary>
	public class GestureManager : ILoadable, IGestureManager
	{
		#region Private Variables

		// Create variable to hold the only allowed instance of this class
        static readonly GestureManager _Instance;

		// Create read/write list of IGestures to hold system gestures
		List<IGesture> _Gestures = new List<IGesture>();

		// Create PointPatternAnalyzer to process gestures when received
		PointPatternAnalyzer gestureAnalyzer = null;

        IPluginInfo _cachedPluginInfo;
        PointF? _cachedPoint;
		#endregion

		#region Public Instance Properties

		public string GestureName { get; set; }
		public IGesture[] Gestures
		{
			get
			{
				if (_Gestures == null)
					_Gestures = new List<IGesture>();

				return _Gestures.ToArray();
			}
		}

		#endregion

		#region Constructors

        static GestureManager()
        {
            _Instance = new GestureManager();
            PluginManager.Instance.Setup(_Instance);
        }
		protected GestureManager()
		{
			if (!LoadGestures())
				_Gestures = new List<IGesture>();

			// Instantiate gesture analyzer using gestures loaded from file
			gestureAnalyzer = new PointPatternAnalyzer(Gestures);

			// Wireup event to mouse capture class to catch points captured
            Subscribe();

            // Wireup event to mouse capture class to catch points captured when setting has been changed
            Properties.Settings.Default.PropertyChanged += Default_PropertyChanged;
            
            
			// Reload gestures if options were saved
			HighSign.UI.FormManager.Instance.InstanceRequested += (o, e) =>
			{
				if (e.Instance is HighSign.UI.Forms.Options)
					(e.Instance as HighSign.UI.Forms.Options).OptionsSaved += (so, se) => { LoadGestures(); };
			};
		}

        void Default_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ShowHint" || e.PropertyName == "Teaching")
            {
                Input.MouseCapture.Instance.PointCaptured -= MouseCapture_PointCaptured;
                PluginManager.Instance.GestureRecognized -= PluginManager_GestureRecognized;
                Input.MouseCapture.Instance.BeforePointsCaptured -= MouseCapture_BeforePointsCaptured;
                Subscribe();
            }
        }

        private void Subscribe()
        {
            if (Properties.Settings.Default.ShowHint && !Properties.Settings.Default.Teaching)
            {
                Input.MouseCapture.Instance.PointCaptured += MouseCapture_PointCaptured;
                PluginManager.Instance.GestureRecognized += PluginManager_GestureRecognized;
            }
            else
            {
                Input.MouseCapture.Instance.BeforePointsCaptured += MouseCapture_BeforePointsCaptured;
            }
            Input.MouseCapture.Instance.AfterPointsCaptured += MouseCapture_AfterPointsCaptured;
        }

        

		#endregion

		#region Public Type Properties

		public static GestureManager Instance
		{
			get { return _Instance; }
		}

		#endregion

		#region Events

        /// <summary>
        /// NOTE: working when ShowHint is true and Teaching is false
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void MouseCapture_PointCaptured(object sender, PointsCapturedEventArgs e)
        {
            // get all the points recoreded since the start of the gesture
            PointF[] captured = Input.MouseCapture.Instance.GetCapturedPoints();

            // cache the start point of the gesture for further use in MouseCapture_AfterPointsCaptured
            if (_cachedPoint == null)
                _cachedPoint = e.CapturePoint;

            // ignore small mouse moves
            if (captured.Count() > 5)
            {
                // start the recognition of recorded points
                this.GestureName = GetGestureName(captured);
                if (this.GestureName == null)
                {
                    // if is undifined gesture, clear the _cachedPluginInfo to prevent unexpected 
                    // recognition in MouseCapture_AfterPointsCaptured
                    _cachedPluginInfo = null;

                    // clear hint
                    HighSign.UI.FormManager.Instance.Surface.DrawHint(null);
                }
                else
                {
                    // process the recognized gesture. (ie. finding the related plugin)
                    RecognizeGesture(captured, _cachedPoint.Value);
                }
                
            }
        }

        // 
        /// <summary>
        /// NOTE: working when ShowHint is true and Teaching is false
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PluginManager_GestureRecognized(object sender, ExecutionPrepareEventArgs e)
        {
            if (e.PlugInfo == null)
            {
                // if PluginManager didn't recognize the gesture, then clear _cachedPluginInfo
                // and hint
                _cachedPluginInfo = null;
                HighSign.UI.FormManager.Instance.Surface.DrawHint(null);
            }
            else
            {
                // else, PluginManager recognized a defined gesture, cache the relatied PlugInfo
                // for further use in MouseCapture_AfterPointsCaptured. And draw hint
                _cachedPluginInfo = e.PlugInfo;
                HighSign.UI.FormManager.Instance.Surface.DrawHint(e.Action.Name);
            }
        }

        /// <summary>
        /// NOTE : working when ShowHint is false or Teaching is true
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void MouseCapture_BeforePointsCaptured(object sender, PointsCapturedEventArgs e)
        {
            this.GestureName = GetGestureName(e.Points);
        }

        /// <summary>
        /// NOTE : always working
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void MouseCapture_AfterPointsCaptured(object sender, PointsCapturedEventArgs e)
        {
            if (Properties.Settings.Default.ShowHint && !Properties.Settings.Default.Teaching)
            {
                // if a defined gesture is recognized, a related PluginInfo will be stored in _cachedPluginInfo
                // and a start point will be also stored in _cachedPoint
                if (_cachedPluginInfo != null && _cachedPoint != null)
                {
                    // tell the Plugin to perform it's execution
                    // and clear all the caches to prepare for next
                    // recognition
                    PluginManager.Instance.ExecutePlugin(_cachedPluginInfo, new PointInfo(_cachedPoint.Value));
                    _cachedPluginInfo = null;
                    _cachedPoint = null;
                }
            }
            else
            {
                RecognizeGesture(e.Points, e.CapturePoint);
            }
        }

		#endregion

		#region Custom Events

		// Define events to allow other classes to subscribe to
		public event RecognitionEventHandler GestureRecognized;
		public event RecognitionEventHandler GestureNotRecognized;

		// Define events to notify subscribers that a gesture has been deleted
		public event GestureEventHandler GestureDeleted;

		// Define protected method to notifiy subscribers of events
		protected virtual void OnGestureRecognized(RecognitionEventArgs e)
		{
			if (GestureRecognized != null) GestureRecognized(this, e);
		}

		protected virtual void OnGestureNotRecognized(RecognitionEventArgs e)
		{
			if (GestureNotRecognized != null) GestureNotRecognized(this, e);
		}

		protected virtual void OnGestureDeleted(GestureEventArgs e)
		{
			if (GestureDeleted != null) GestureDeleted(this, e);
		}

		#endregion

		#region Private Methods

		private void RecognizeGesture(PointF[] Points, PointF CapturePoint)
		{
			// Fire recognized event if we found a gesture match, otherwise throw not recognized event
			if (GestureName != null)
				OnGestureRecognized(new RecognitionEventArgs(GestureName, Points, CapturePoint));
			else
				OnGestureNotRecognized(new RecognitionEventArgs(Points, CapturePoint));
		}

        private void PerformGesture()
        {
        }

		#endregion

		#region Public Methods

		public void Load()
		{
			// Shortcut method to control singleton instantiation
		}

		public void AddGesture(IGesture Gesture)
		{
			_Gestures.Add(Gesture);
		}

		public bool LoadGestures()
		{
			try
			{
				// Load gestures from file, create empty list if load failed
				_Gestures = Configuration.IO.FileManager.LoadObject<List<IGesture>>("Gestures.json", new Type[] { typeof(Gesture) });

				if (Gestures == null)
					return false;
				else
					return true;
			}
			catch
			{
				return false;
			}
		}

		public bool SaveGestures()
		{
			try
			{
				// Save gestures to file
				Configuration.IO.FileManager.SaveObject<List<IGesture>>(Gestures, "Gestures.json");

				return true;
			}
			catch
			{
				return false;
			}
		}

		public string GetGestureName(PointF[] Points)
		{
			// Get closest match, if no match, exit method
			return GetGestureSetNameMatch(Points);
		}

		public string GetGestureSetNameMatch(PointF[] Points)
		{
			// Update gesture analyzer with latest gestures and get gesture match from current points array
			// Comparison results are sorted descending from highest to lowest probability
			gestureAnalyzer.PointPatternSet = Gestures.ToArray();
			PointPatternMatchResult[] comparisonResults = gestureAnalyzer.GetPointPatternMatchResults(Points);

			// Exit if we didn't find a high probability match
			if (comparisonResults == null || comparisonResults.Where(ppmr => ppmr.Probability >= 75).Count() <= 0)
				return null;		// No close enough match. Do nothing with drawn gesture

			// Grab top result from gesture comparison
			return comparisonResults.First().Name;
		}

		public string[] GetAvailableGestures()
		{
			return Gestures.OrderBy(g => g.Name).GroupBy(g => g.Name).Select(g => g.Key).ToArray();
		}

		public bool GestureExists(string GestureName)
		{
			return _Gestures.Exists(g => g.Name.ToLower() == GestureName.Trim().ToLower());
		}

		public IGesture GetNewestGestureSample(string GestureName)
		{
			return Gestures.LastOrDefault(g => g.Name.ToLower() == GestureName.Trim().ToLower());
		}

		public void DeleteGesture(string GestureName)
		{
			_Gestures.RemoveAll(g => g.Name.ToLower().Trim() == GestureName.Trim().ToLower());
			SaveGestures();

			OnGestureDeleted(new GestureEventArgs(GestureName));
		}

		#endregion
	}
}
